package cn.edu.hitsz.compiler.asm;

import java.util.Set;
import java.util.stream.Collectors;

/**
 * 汇编语句类
 * <br>
 * 鉴于汇编语句种类确定，故在此采用enum定义汇编语句种类并自行在各个 getter 中判断对该种类的操作是否合法
 * <br>
 * 如果需要扩展 Assembly, 你可以修改此文件. 但是可能需要同步修改 AssemblyGenerator, AssemblyKind 中的内容.
 */
public class Assembly {
    //============================== 不同种类 Assembly 的构造函数 ==============================
    public static Assembly createAdd(Reg rd, Reg rs1, Reg rs2) {
        return new Assembly(AssemblyKind.add, rd, rs1, rs2, 0);
    }

    public static Assembly createSub(Reg rd, Reg rs1, Reg rs2) {
        return new Assembly(AssemblyKind.sub, rd, rs1, rs2, 0);
    }

    public static Assembly createMul(Reg rd, Reg rs1, Reg rs2) {
        return new Assembly(AssemblyKind.mul, rd, rs1, rs2, 0);
    }

    public static Assembly createLi(Reg rd, int imm) {
        return new Assembly(AssemblyKind.li, rd, null,null, imm);
    }

    public static Assembly createAddi(Reg rd, Reg rs1, int imm) {
        return new Assembly(AssemblyKind.addi, rd, rs1, null, imm);
    }

    //============================== 不同种类 Assembly 的参数 getter ==============================

    public AssemblyKind getKind() {
        return kind;
    }

    public Reg getRd() { return rd; }

    public Reg getRs1() {
        ensureKindMatch(Set.of(AssemblyKind.add,AssemblyKind.addi,AssemblyKind.sub,AssemblyKind.mul));
        return rs1;
    }

    public Reg getRs2() {
        ensureKindMatch(Set.of(AssemblyKind.add,AssemblyKind.mul,AssemblyKind.sub));
        return rs2;
    }

    public int getImm() {
        ensureKindMatch(Set.of(AssemblyKind.li,AssemblyKind.addi));
        return imm;
    }

    //============================== 基础设施 ==============================//

    public String toString() {
        final var kindString = kind.toString();
        final var rdString = rd.toString();
        final var rs1String = rs1 == null ? "" : rs1.toString();
        final var rs2String = rs2 == null ? "" : rs2.toString();
        final var immString = String.valueOf(imm);
        switch (this.getKind()){
            case li -> {
                return "%s %s, %s".formatted(kindString, rdString, immString);
            }
            case add, sub, mul -> {
                return "%s %s, %s, %s".formatted(kindString, rdString, rs1String, rs2String);
            }
            case addi -> {
                return "%s %s, %s, %s".formatted(kindString, rdString, rs1String, immString);
            }
        }
        throw new RuntimeException("unknown assembly type");
    }

    private Assembly(AssemblyKind kind, Reg rd, Reg rs1, Reg rs2, int imm) {
        this.kind = kind;
        this.rd = rd;
        this.rs1 = rs1;
        this.rs2 = rs2;
        this.imm = imm;
    }

    private final AssemblyKind kind;
    private final Reg rd;
    private final Reg rs1;
    private final Reg rs2;
    private final int imm;

    private void ensureKindMatch(Set<AssemblyKind> targetKinds) {
        final var kind = getKind();
        if (!targetKinds.contains(kind)) {
            final var acceptKindsString = targetKinds.stream()
                    .map(AssemblyKind::toString)
                    .collect(Collectors.joining(","));

            throw new RuntimeException(
                    "Illegal operand access, except %s, but given %s".formatted(acceptKindsString, kind));
        }
    }
}
